import { computed, ComputedRef, onMounted, Ref, ref } from 'vue';
import { TApiFun } from './type';
import { hookResult } from './bindResult';

export type DefaultStatus = 'loading' | 'success';

export type SkeletonStatus = DefaultStatus | 'error';

export interface IUseSkeletonProps<TData, TParams extends any[]> {
  apiFun: TApiFun<TData, TParams>;
  placeholderResult?: TData;
  immediate?: boolean;
  initQueryParams?: TParams;
  transformDataFun?: (data: TData) => TData | Promise<TData>;
  updateParamsOnFetch?: boolean;
  defaultStatus?: DefaultStatus;
  onSuccess?: (data: any) => void;
  onError?: (e: any) => void;
  isEmpty?: (data: TData) => boolean;
  coverLoading?: boolean;
}

// type UnwrapNestedRefs<T> = ReturnType<typeof reactive>;

type DataType<T> = T | null | unknown | undefined;

export type IAutoSkeletonViewResult<TData, TParams extends any[]> = {
  execute: TApiFun<TData, TParams>;
  data: DataType<TData>;
  retry: () => Promise<TData>;
  loading: ComputedRef<boolean>;
  status: Ref<SkeletonStatus>;
  getField: (key: string) => any;
  bindProps: {
    data: DataType<TData>;
    status: SkeletonStatus;
    errorMsg: string;
    placeholderResult?: TData;
    isEmpty?: (data: TData) => boolean;
    onRetry: () => Promise<TData>;
  };
};

export function useSkeleton<TData = any, TParams extends any[] = any[]>(
  prop: IUseSkeletonProps<TData, TParams>
): IAutoSkeletonViewResult<TData, TParams> {
  const {
    coverLoading,
    apiFun,
    onError,
    defaultStatus = 'loading',
    placeholderResult,
    isEmpty,
    initQueryParams = [],
    transformDataFun,
    onSuccess,
    updateParamsOnFetch = true,
    immediate = true,
  } = prop;

  const status = ref<SkeletonStatus>(defaultStatus);

  const data = ref<DataType<TData>>(null);

  const placeholder: Ref<DataType<TData>> =
    ref<DataType<TData>>(placeholderResult);

  const errorMsg = ref('');

  const lastFetchParams = ref<TParams>(initQueryParams as TParams);

  const executeApiFun: TApiFun<TData, TParams> = (...params: TParams) => {
    if (updateParamsOnFetch) {
      // @ts-ignore
      lastFetchParams.value = params;
    }

    status.value = 'loading';

    return apiFun(...params)
      .then(async (res) => {
        let resData: any = res;
        if (transformDataFun) {
          resData = await Promise.resolve(transformDataFun(res));
        }
        placeholder.value = resData;
        data.value = resData;
        status.value = 'success';
        if (onSuccess) {
          onSuccess(resData);
        }
        return resData;
      })
      .catch((e) => {
        status.value = 'error';
        errorMsg.value = e.message;
        if (onError) {
          onError(e);
        }
        throw e;
      });
  };

  function retry() {
    return executeApiFun(...(lastFetchParams.value as TParams));
  }

  onMounted(() => {
    if (immediate && defaultStatus === 'loading') {
      executeApiFun(...(initQueryParams as TParams));
    }
  });

  const loading = computed(() => {
    return status.value === 'loading';
  });

  function getField(key: string) {
    if (status.value !== 'success') {
      return '';
    }
    if (data.value) {
      // @ts-ignore
      return data.value[key];
    }
    return '';
  }

  // @ts-ignore
  return hookResult(
    {
      execute: executeApiFun,
      data,
      retry,
      loading,
      status,
      getField,
    },
    {
      data,
      status,
      errorMsg,
      placeholderResult: placeholder,
      isEmpty,
      coverLoading,
      onRetry: retry,
    }
  );
}
